
import threading
from coffeetalk.protocol import ioml, requests, responses, iotypes
from coffeetalk.utilities import objectmaker

class ClientException(Exception):
    
    def __init__(self,errorMessage):
        self.errorMessage = errorMessage

    def __str__(self):
        return self.errorMessage

class InternalClient(object):
    
    def __init__(self,systemtree):
        
        self.subscriptionlock = threading.RLock()
        self.subscriptions = {}
        self.iotree = systemtree
        
        self.objectmaker = objectmaker.ObjectMaker()

        self.env = iotypes.Environment('0.0.0.0','internalclient.py','Internal',str(id(self)),'0')
    
    def dispatch_message(self,message):
        self.subscriptionlock.acquire()
        try:
            iomessage = ioml.IOML(iomlstring=message)
            if self.subscriptions.has_key(iomessage.headelement.attribs['path']):
                
                env = iotypes.Environment('','','','','',element=iomessage.headelement.childelements['ENV'])
                if iomessage.headelement.attribs['type'] == 'updates.Add':
                    newobj = self.objectmaker.make_object(iomessage.bodyelement)
                    for v in self.subscriptions[iomessage.headelement.attribs['path']].itervalues():
                        if v.rawelement == False:
                            self._call_on_add_handler(v, env, newobj)
                        else:
                            self._call_on_add_handler(v,env,iomessage.bodyelement)
                elif iomessage.headelement.attribs['type'] == 'updates.Delete':
                    for v in self.subscriptions[iomessage.headelement.attribs['path']].itervalues():
                        self._call_on_delete_handler(v, env, iomessage.headelement.attribs['key'])   
        except:
            self.subscriptionlock.release()
        finally:
            self.subscriptionlock.release()
    
    def _call_on_success_handler(self,request,env):
        
        if request.on_success is None: return
        try:
            request.on_success(env,request.path,**request.kwargs)
        except:
            pass
    
    def _call_on_error_handler(self,request,env,err):
        
        if request.on_error is None: return
        try:
            request.on_error(env,request.path,request,err,**request.kwargs)
        except:
            pass
    
    def _call_on_get_handler(self,request,env,element):
        if request.on_get is None: return
        try:
            request.on_get(env,request.path,self.objectmaker.make_object(element),**request.kwargs)
        except:
            pass
        pass
    
    def _call_on_add_handler(self,subscription,env,element):

        if subscription.on_add is None: return
        try:
            subscription.on_add(env,subscription.path,element,**subscription.kwargs)
        except:
            pass
    
    def _call_on_delete_handler(self,subscription,env,key):

        if subscription.on_delete is None: return
        try:
            subscription.on_delete(env,subscription.path,key,**subscription.kwargs)
        except:
            pass
            
    def get(self,getrequest):
        
        if not isinstance(getrequest,requests.Get):
            raise ClientException('request must be a get request')
        
        response = self.iotree.get_node(self.env, getrequest.path)
        
        if isinstance(response,responses.OK):
            if response.bodyelement is not None:
                self._call_on_get_handler(getrequest, self.env, response.bodyelement)
        else:
            self._call_on_error_handler(getrequest, self.env, response)
        
      
    def add(self,addrequest):
        if not isinstance(addrequest,requests.Add):
            raise ClientException('request must be an add request')
        
        response = self.iotree.add_node(self.env,addrequest.path,addrequest.iobject._to_element())
        
        if isinstance(response,responses.OK):
            self._call_on_success_handler(addrequest, self.env)
        else:
            self._call_on_error_handler(addrequest, self.env, response)

    def delete(self,deleterequest):
        if not isinstance(deleterequest,requests.Delete):
            raise ClientException('request must be a delete request')
        
        key = deleterequest.key
        response = self.iotree.delete_node(self.env,deleterequest.path,key)
        
        if isinstance(response,responses.OK):
            self._call_on_success_handler(deleterequest, self.env)
        else:
            self._call_on_error_handler(deleterequest, self.env, response)
        
    def subscribe(self,subrequest):
        
        if not isinstance(subrequest,requests.Subscribe):
            raise ClientException('request must be a subscribe request')
        
        response = None
        self.subscriptionlock.acquire()
        
        if self.subscriptions.has_key(subrequest.path):
            response = self.iotree.get_node(self.env,subrequest.path)
        else:
            response = self.iotree.add_subscription(self.env,subrequest.path,self.dispatch_message)
            
        if isinstance(response,responses.OK):
            if not self.subscriptions.has_key(subrequest.path):
                self.subscriptions[subrequest.path] = {}  
            self.subscriptions[subrequest.path][str(subrequest)] = subrequest           
            self.subscriptionlock.release()

            self._call_on_success_handler(subrequest, self.env)
            
            if response.bodyelement is not None:
                rootnode = response.bodyelement
                for child in rootnode.itervalues():
                    self._call_on_add_handler(subrequest, self.env, self.objectmaker.make_object(child))
        else:
            self.subscriptionlock.release()
            self._call_on_error_handler(subrequest, self.env, response)
              
    def unsubscribe(self,unsubrequest):
        
        if not isinstance(unsubrequest,requests.Unsubscribe):
            raise ClientException('request must be an unsubscribe request')
        
        # So how are we going to do this???
        # The precedence for finding the subscription to delete goes as follows:
        # the path if and only if it is the only subscriber
        # the id of the subscription's on_add function
        # the id of the subscription's on_delete function
        self.subscriptionlock.acquire()
        
        if self.subscriptions.has_key(unsubrequest.path):
            if len(self.subscriptions[unsubrequest.path]) == 0:
                # We only have one subscription here so just delete it
                del self.subscriptions[unsubrequest.path]
                response = self.iotree.delete_subscription(self.env,unsubrequest.path,str(self.dispatch_message))
            
                if isinstance(response,responses.OK):
                    self._call_on_success_handler(unsubrequest, self.env)
                else:
                    self._call_on_error_handler(unsubrequest, self.env, response)
            else:
                # We have multiple subscriptions here. Loop through looking for the one to delete
                # The rule is look for on_add matches first and on_delete second
                sub = None
                for k,v in self.subscriptions[unsubrequest.path].iteritems():
                    if id(v.on_add) == id(unsubrequest.on_add):
                        sub = k
                if sub is None:
                    for k,v in self.subscriptions[unsubrequest.path].iteritems():
                        if id(v.on_delete) == id(unsubrequest.on_delete):
                            sub = v
                if sub is None:
                    raise ClientException('Subscription not found')
                
                if self.subscriptions[unsubrequest.path].has_key(sub):
                    del self.subscription[unsubrequest.path]
                    self._call_on_success_handler(unsubrequest, self.env)
            
            self.subscriptionlock.release()
        
        else:
            raise ClientException('Subscription not found')
    
    def add_monitor(self,request,path):
        #TODO: make this work like subscriptions
        return
#        monitortype = request.headelement.attribs['monitortype']
#        response = self._find_iotree(path).add_monitor(self.env,path,monitortype,self.connection.send_message)
#        if isinstance(response,responses.OK):
#            self.monitorlock.acquire()
#            self.monitors[path] = '1'
#            self.monitorlock.release()
#        return response
        
    def delete_monitor(self,request,path):
        return
        #TODO: make this work like subscriptions
#        monitortype = request.headelement.attribs['monitortype']
#        response = self._find_iotree(path).delete_monitor(self.env,path,monitortype,str(self.connection.send_message))
#        if isinstance(response,responses.OK):
#            self.monitorlock.acquire()
#            del self.monitors[path]
#            self.monitorlock.release()
#        return response
        
                
        
    